﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Compiler
{
    //problem, wenn lookahead ein HashSet<Symbol> ist:
    //Z-> .A {a}
    //Z-> .A {b}
    //wird nicht als equivalent betrachtet mit
    //Z-> .A {a,b}
    //die algorithmen produzieren LR1Items mit unvollständigen Mengen. Deshalb ist es sinnvoller, die Items generell auf einzelne
    //lookahead-elemente zu beschränken
    //-> kanonische speicherung
    public class LR1Item
    {
        private readonly LR0Item lr0item;
        private Symbol lookahead;
        private Symbol.SymbolEqualityComparer SymbolCmp = new Symbol.SymbolEqualityComparer();

        public LR1Item(LR0Item lr0item, Symbol lookahead)
        {
            this.lr0item = lr0item;
            this.lookahead = lookahead;
        }

        public static HashSet<LR1Item> ItemMerger(HashSet<LR1Item> first, HashSet<LR1Item> second)
        {
            var final = new HashSet<LR1Item>(first, new LR1Item.LR1ItemEqualityComparer());
            final.UnionWith(second);
            return final;
        }

        public Symbol GetSymbolOnRightSide()
        {
            return lr0item.GetSymbolOnRightside();
        }

        public bool IsItemWithDotFarRight()
        {
            return lr0item.IsItemWithDotFarRight();
        }

        public Production getItemProduction()
        {
            return lr0item.production;
        }

        public bool LookaheadIs(Symbol term)
        {
            return SymbolCmp.Equals(lookahead, term); //lookahead.Contains(term);
        }

        public static HashSet<LR1Item> Goto1(Grammar grammar, HashSet<LR1Item> I, Symbol X)
        {
            LR1ItemEqualityComparer lr1Cmp = new LR1ItemEqualityComparer();
            HashSet<LR1Item> final = new HashSet<LR1Item>(lr1Cmp);

            foreach (LR1Item item in I)
            {
                if (item.lr0item.IsSymbolOnRightside(X))
                    final.Add(new LR1Item(LR0Item.CreateItem_MoveDotForwad(item.lr0item), item.lookahead));
            }

            return Closure1(grammar, final);
        }

        public static HashSet<LR1Item> Closure1(Grammar grammar, HashSet<LR1Item> itemSet)
        {
            LR1ItemEqualityComparer lr1Cmp = new LR1ItemEqualityComparer();
            HashSet<LR1Item> final = new HashSet<LR1Item>(itemSet, lr1Cmp);
            HashSet<LR1Item> working = new HashSet<LR1Item>(itemSet, lr1Cmp);

            var FirstSets = SetFactory.CreateFirstSets(grammar);
            bool smthChanged = false;
            do
            {
                HashSet<LR1Item> newItems = new HashSet<LR1Item>(lr1Cmp);
                int finalCount = final.Count;

                foreach (LR1Item item in working)
                {
                    Nonterminal nt;
                    List<Symbol> restChain;
                    if (item.lr0item.NonterminalOnDotsRightside(out nt, out restChain))
                    {
                        List<Production> prodForMeta = grammar.Productions[nt];
                        foreach (var production in prodForMeta)
                        {
                            var firstSetForChain = FirstSets.ForChain(Util.Concat(restChain, item.lookahead));

                            foreach (var symbol in firstSetForChain)
                            {
                                newItems.Add(new LR1Item(
                                                LR0Item.CreateItem_DotAtStart(production),
                                                symbol));
                            }
                        }
                    }
                }

                final.UnionWith(newItems);
                working = newItems;
                smthChanged = finalCount != final.Count;
            } while (smthChanged);

            return final;
        }

        public override string ToString()
        {
            return "[" + lr0item.ToString() + "  ,  " + lookahead.ToString() + "]";
        }

        public string ToStringLR0Item()
        {
            return lr0item.ToString();
        }

        public string ToStringLookaheadSet()
        {
            return lookahead.ToString();
        }
        
        public class LR1ItemEqualityComparer : IEqualityComparer<LR1Item>
        {
            LR0Item.LR0ItemEqualityComparer lr0Cmp = new LR0Item.LR0ItemEqualityComparer();
            Symbol.SymbolEqualityComparer symbolCmp = new Symbol.SymbolEqualityComparer();

            public bool Equals(LR1Item x, LR1Item y)
            {
                return lr0Cmp.Equals(x.lr0item, y.lr0item) && symbolCmp.Equals(x.lookahead, y.lookahead);
            }

            public int GetHashCode(LR1Item obj)
            {
                return lr0Cmp.GetHashCode(obj.lr0item) + 17 * symbolCmp.GetHashCode(obj.lookahead);
            }
        }

        public class LR1KernelEqualityComparer : IEqualityComparer<LR1Item>
        {
            LR0Item.LR0ItemEqualityComparer lr0Cmp = new LR0Item.LR0ItemEqualityComparer();

            public bool Equals(LR1Item x, LR1Item y)
            {
                return lr0Cmp.Equals(x.lr0item, y.lr0item);
            }

            public int GetHashCode(LR1Item obj)
            {
                return lr0Cmp.GetHashCode(obj.lr0item);
            }
        }

        public class KernelSetComparer : IEqualityComparer<HashSet<LR1Item>>
        {
            LR1KernelEqualityComparer kernelCmp = new LR1KernelEqualityComparer();

            public bool Equals(HashSet<LR1Item> x, HashSet<LR1Item> y)
            {
                if (ReferenceEquals(x, y))
                    return true;

                HashSet<LR1Item> redX = x; 
                HashSet<LR1Item> redY = y; 

                if(x.Comparer.GetType() != kernelCmp.GetType())
                    redX = new HashSet<LR1Item>(x, kernelCmp);
                if(y.Comparer.GetType() != kernelCmp.GetType())
                    redY = new HashSet<LR1Item>(y, kernelCmp); 

                return redX.SetEquals(redY);
            }

            public int GetHashCode(HashSet<LR1Item> obj)
            {
                HashSet<LR1Item> redObj = obj;

                if(obj.Comparer.GetType() != kernelCmp.GetType())
                    redObj = new HashSet<LR1Item>(obj, kernelCmp);

                int hash = 0;
                foreach (var item in redObj)
                    hash += kernelCmp.GetHashCode(item);

                return hash;
            }
        }
    }
}
